package cs340.stormtroopers.hypeerweb;

import java.io.IOException;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Random;

import cs340.stormtroopers.database.HyPeerWebDatabase;

/**
 * The controller for the HyPeerWeb <br>
 * 
 * <pre>
 * <b>Domain</b>
 * 		nodes		: Map<Integer, Node>
 * 		database	: HyPeerWebDatabase
 * 		random		: Random
 * 		
 * <b>Invariant</b>
 * 		nodes is a non-null HashMap which maps a webId value to a Node object
 * 		database is a non-null singleton object that connects to a database
 * 		random is a non-null Random object
 * </pre>
 * 
 * @author Andy Morrow
 * @author Brad Spendlove
 * @author Daniel Howard
 * @author James Gurr
 * @author William Highland
 */
public class HyPeerWeb
{
	/**
	 * Singleton object with which to interact with the HyPeerWeb
	 */
	private static HyPeerWeb singleton = null;

	/**
	 * Map containing the Nodes in the HyPeerWeb
	 */
	private HashMap<Integer, Node> nodes;
	/**
	 * The HyPeerWebDatabase object with which to access the database
	 */
	private HyPeerWebDatabase database;
	/**
	 * Random object for generating random numbers
	 */
	private final Random random = new Random();

	/**
	 * Private constructor for creation of the singleton object
	 * 
	 * @pre none
	 * @post A new HyPeerWeb object is created for the singleton. Domain is
	 *       initialized.
	 */
	private HyPeerWeb()
	{
		nodes = new HashMap<Integer, Node>();
		try
		{
			HyPeerWebDatabase.initHyPeerWebDatabase();
			database = HyPeerWebDatabase.getSingleton();
		}
		catch (IOException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		catch (ClassNotFoundException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		catch (SQLException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Add a new complete node to the HyPeerWeb Does not manage connections
	 * 
	 * @param node
	 *            The new Node to add to the HyPeerWeb
	 * 
	 * @pre The size of nodes will be n [0-*)
	 * @post The node is added to the HyPeerWeb. nodes' size is increased by one
	 *       The new Node is accessible by its webId value
	 */
	public void addNode(Node node)
	{
		nodes.put(node.getWebId().hashCode(), node);
	}

	/**
	 * Adds a new node to the HyPeerWeb Manages connections
	 * 
	 * @param newNode
	 *            The new node to add to the HyPeerWeb
	 * @param startNode
	 *            The starting point for which to scan the HyPeerWeb for the
	 *            insertion point If startNode is null, it chooses a random
	 *            start point
	 * 
	 * @pre The size of nodes will be n [0-*) if the hypercube is incomplete:
	 *      Down pointers and up pointers and possibly (inverse) surrogate folds
	 *      will exist else: No up or down pointers and no surrogate folds will
	 *      exist
	 * @post The size of nodes will increase by 1 if the hypercube was
	 *       incomplete: Up pointers and down pointers will have been removed,
	 *       as will (inverse) surrogate folds The new node will have down
	 *       pointers to all of its parents nodes bigger than it The parent will
	 *       give the new node its fold, and adopt a surrogate fold unless it
	 *       already had an inverse surrogate fold, which will result in that
	 *       surrogate fold being removed else The hypercube will now be
	 *       incomplete, with the new node above Node 0 Node 0 will no adopt a
	 *       surrogate fold equalling its old fold The new node will have Node
	 *       0's fold as a fold The new node will have down pointers equal to it
	 *       parent's neighbors bigger than it.
	 */
	public void addToHyPeerWeb(Node newNode, Node startNode)
	{
		if (nodes.size() == 0)
		{
			newNode.setWebId(new WebId(0));
			newNode.setFold(newNode);
			addNode(newNode);
		}
		else
		{
			if (startNode == null)
			{
				// assumption: there are no gaps in the numbering

				// if this is the assumption shouldn't we start with the largest
				// node in nodes?
				int randomWebId = random.nextInt(nodes.size());
				startNode = nodes.get(new Integer(randomWebId));
				// startNode = nodes.get(nodes.size()-1); //?
			}
			startNode.addToHyPeerWeb(newNode);
			// we do NOT call addNode(newNode) here
			// for a good reason
			// see the comment in the Node.addToHyPeerWeb() method
		}
	}

	/**
	 * Remove a node from the HyPeerWeb Manages connections
	 * 
	 * @param nodeToDelete
	 *            The node which is to be removed from the HyPeerWeb
	 * 
	 * @pre nodes will hold 2+ nodes
	 * @post The size of nodes will be reduced by 1 The node with the highest
	 *       webId will take the removed node's position in the HyPeerWeb The
	 *       replacing node will adopt all the connections once held by the
	 *       removed node and have those connections adopt the replacing node in
	 *       place of the removed node The replacing node will drop all its
	 *       former connections The removed node will drop its connections, and
	 *       its connections will drop it from theirs
	 */
	public void removeFromHyPeerWeb(Node nodeToDelete)
	{
		nodeToDelete.removeFromHyPeerWeb();
		// the following is instead done in the Node class
		// parallel to the addToHyPeerWeb method
		// removeNode(nodeToDelete);
	}

	/**
	 * Clears all the nodes from the HyPeerWeb
	 * 
	 * @pre A HyPeerWeb made up of 0+ nodes
	 * @post A HyPeerWeb holding 0 nodes
	 */
	public void clear()
	{
		nodes.clear();
	}

	/**
	 * Determines whether the passed node is a member of the HyPeerWeb
	 * 
	 * @param node
	 *            The node to search for in the HyPeerWeb
	 * @return True when the passed node is present, and false otherwise
	 * 
	 * @pre none
	 * @post none
	 */
	public boolean contains(Node node)
	{
		return nodes.containsKey(node.getWebId().hashCode());
	}

	/**
	 * Returns the database object held by the HyPeerWeb
	 * 
	 * @return The HyPeerWebDatabase object held by the HyPeerWeb
	 * 
	 * @pre none
	 * @post none
	 */
	public HyPeerWebDatabase getHyPeerWebDatabase()
	{
		return database;
	}

	/**
	 * Returns the node with the passed webId
	 * 
	 * @param i
	 *            The webId of the node being sought after
	 * @return The node with the passed webId or null if no such node exists
	 * 
	 * @pre none
	 * @post none
	 */
	public Node getNode(int i)
	{
		return nodes.get(i);
	}

	/**
	 * Returns the singleton object for the HyPeerWeb
	 * 
	 * @return The singleton object for the HyPeerWeb
	 * 
	 * @pre none
	 * @post none
	 */
	public static HyPeerWeb getSingleton()
	{
		if (singleton == null)
			singleton = new HyPeerWeb();

		return singleton;
	}

	/**
	 * Reloads the information held in the database into the HyPeerWeb Removes
	 * any unsaved data in the process Reloads from the default database
	 * 
	 * @pre A HyPeerWeb of 0+ nodes of some arbitrary state
	 * @post A HyPeerWeb of 0+ nodes reflecting the state of the HyPeerWeb at
	 *       the time of the last save operation If some error is raised when
	 *       accessing the database, no change will occur
	 */
	public void reload()
	{
		reload(null);
	}

	/**
	 * Reloads the information held in the passed database into the HyPeerWeb
	 * Removes any unsaved data in the process
	 * 
	 * @param dbName
	 *            The database from which to load the data for the reloaded
	 *            HyPeerWeb
	 * 
	 * @pre A HyPeerWeb of 0+ nodes of arbitrary state
	 * @post A HyPeerWeb of 0+ nodes reflecting the state of the HyPeerWeb at
	 *       the time of the last save operation performed on the passed
	 *       database If some error is raised when accessing the database, no
	 *       change will occur
	 */
	public void reload(java.lang.String dbName)
	{
		try
		{
			if (dbName == null)
				HyPeerWebDatabase.initHyPeerWebDatabase();
			else
				HyPeerWebDatabase.initHyPeerWebDatabase(dbName);
			database = HyPeerWebDatabase.getSingleton();
		}
		catch (IOException i)
		{
			System.out.println(i);
			return;
		}
		catch (ClassNotFoundException c)
		{
			System.out.println(c);
			return;
		}
		catch (SQLException s)
		{
			System.out.println(s);
			return;
		}

		nodes.clear(); // it won't clear the map if it can't load the database

		HyPeerWebBuilder builder;
		try
		{
			builder = new HyPeerWebBuilder(database.getCompleteNodeDomainSet());
			nodes.putAll(builder.getNodeSet());
		}
		catch (SQLException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Removes a node from the HyPeerWeb Does not manage connections
	 * 
	 * @param node
	 *            The node to be removed from the HyPeerWeb
	 * 
	 * @pre A HyPeerWeb of 0+ nodes
	 * @post The number of nodes in the HyPeerWeb will be reduced by 1 if the
	 *       passed node is a member of the HyPeerWeb
	 */
	public void removeNode(Node node)
	{
		nodes.remove(node.getWebId().hashCode());
	}

	/**
	 * Saves the current state of the HyPeerWeb to the database
	 * 
	 * @pre The database held by the HyPeerWeb will hold the data for some
	 *      arbitrary state
	 * @post The database will hold the information for the current state of the
	 *       HyPeerWeb
	 */
	public void saveToDatabase()
	{
		try
		{
			database.clear();
			database.saveNodes(nodes.values());
		}
		catch (SQLException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		catch (IOException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		catch (ClassNotFoundException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Returns the number of nodes in the HyPeerWeb
	 * 
	 * @return The number of nodes in the HyPeerWeb
	 */
	public int size()
	{
		return nodes.size();
	}
}
